home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / PCGPEV10.ZIP / TEXTURE.TXT < prev    next >
Text File  |  1994-05-10  |  64KB  |  1,567 lines

  1.                             ┌─────────────────┐
  2.                             │ Texture Mapping │
  3.                             └─────────────────┘
  4.  
  5.                  Written for the PC-GPE by Sean Barrett.
  6.  
  7.                ┌───────────────────────────────────────────┐
  8.                │      THIS FILE MAY NOT BE DISTRIBUTED     │
  9.                │ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. │
  10.                └───────────────────────────────────────────┘
  11.  
  12.  
  13. -=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=-
  14. TEXTURE
  15. TEXUE  almost everything you need to know to texture map with the PC
  16. TXR
  17. X
  18. -=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=- -=-=-=-=-=-
  19.  
  20. Copyright 1994 Sean Barrett.
  21. Not for reproduction (electronic
  22. or hardcopy) except for personal use.
  23.  
  24. Contents:
  25.  
  26.   0. warnings
  27.   1. terminology and equations
  28.   2. the basics
  29.       Perfect texture mapping
  30.       DOOM essentials
  31.       Wraparound textures
  32.       Non-rectangular polygons
  33.   3. the hazards
  34.       going off the texture map
  35.       divide by 0
  36.       it'll never be fast enough
  37.   4. the complexities
  38.       handling arbitrarily-angled polygons
  39.       lighting
  40.       slow 16-bit VGA cards
  41.       mipmapping
  42.  
  43.  -=-=-=-=-=-
  44. | Note:  I am not providing any references as I simply
  45. |        derived the math myself and worked out the various
  46. |        techniques for myself (the 32-bit ADC trick was
  47. |        pointed out to me in another context by TJC,
  48. |        author of the Mars demo) over the last two years
  49. |        (since Wolfenstein 3D and Underworld came out).
  50.  -=-=-=-=-=-
  51.  
  52. TEXTURE
  53. TEXUE
  54. TXR   0. Warnings
  55. X
  56.  
  57.   I assume a RIGHT-handed 3D coordinate system, with X positive
  58. to the right, Y positive disappearing into the screen/distance,
  59. and Z positive up.  To adjust this to the typical left-handed
  60. 3D space, simply swap all the 3D Ys & Zs.
  61.  
  62.   I assume the screen space is positive-X to the right,
  63. positive-Y goes down.  Adjust the signs as appropriate for
  64. your system.
  65.  
  66.   I will present code and pseudo-code in C.  I also include
  67. some relatively tight inner loops written in assembly, but I'm
  68. omitting the details of the loop setup.  The inner loops, while
  69. usually from real, working code, should generally be taken
  70. as examples showing how fast it ought to be possible to run
  71. a given task, not as necessarily perfect examples.  I often
  72. use 32-bit instructions (sorry 286 programmers) because they
  73. can double the performance.  However, I write in real mode,
  74. because 16-bit addressing is often convenient for texture maps,
  75. and it's straightforward to use segment registers as pointers
  76. to texture maps.  The translation to protected mode should not
  77. prove problematic, but again, these should more be taken as
  78. examples rather than simply being used directly.  I optimize
  79. for the 486, but I skip some obvious optimizations.  For example,
  80. I write "loop", because it's simpler and more clear.
  81. Production code for the 486 should explicitly decrement and
  82. then branch.  Similarly, I write "stosb", etc. etc.
  83.  
  84.  
  85. TEXTURE
  86. TEXUE
  87. TXR   1. Terminology and Equations
  88. X
  89.  
  90.  
  91.   You really probably don't want to read this section first,
  92. but rather refer back to it whenever you feel the need.  So
  93. skip up to section 2 and refer back as appropriate.  I could've
  94. made this an appendix, but it seems too important to put last.
  95.  
  96. TEX
  97. TE   Terms
  98. T
  99.     texture:  A texture is a pixelmap of colors which is mapped
  100.               onto a polygon using "texture mapping".  The size
  101.               of the polygon has nothing to do with the size (the
  102.               number of pixels) in the texture map.
  103.  
  104.     run:  A run is a row or column of pixels.  Normal texture
  105.           mapping routines process one run in an "inner loop".
  106.  
  107.     arbitrarily-angled polygon:  a polygon which isn't a floor,
  108.           wall, or ceiling; technically, a polygon which isn't
  109.           parallel to the X or Z axes (or X or Y axes in a
  110.           Z-is-depth coordinate system).
  111.  
  112.     texture space:
  113.     polygon space:
  114.     polygon coordinate space:
  115.           Since a texture is flat, or two-dimensional, the relation
  116.           of the texture to the 3D world can be described with a
  117.           special coordinate space known by one of these names.
  118.           Because it is only 2D, the space can be characterized with
  119.           the location of the texture space in 2D, and two 3D vectors
  120.           which represent the axes of the coordinate space.  Sometimes
  121.           called "uv" space, because the name of the coordinates are
  122.           usually u & v.
  123.  
  124. TEX
  125. TE   Notation
  126. T
  127.  
  128.     Vectors appear in all caps.
  129.     Components of vectors are P = < Px, Py, Pz >.
  130.  
  131.     Certain variables have consistent usage:
  132.  
  133.       x,y,z are coordinates in three-space
  134.       i,j are screen coordinates
  135.       u,v are coordinates in texture space
  136.       a,b,c are "magic coordinates" such that
  137.          u = a/c, v = b/c
  138.  
  139. TEX
  140. TE   Equations
  141. T
  142.  
  143.     Don't let this scare you off!  Go read section 2, and
  144.     come back to this when you're ready.
  145.  
  146.     Let P,M, and N be vectors defining the texture space: P is the
  147.     origin, and M and N are the vectors for the u&v axes.
  148.  
  149.     Assume these vectors are in _view space_, where view space is
  150.     defined as being the space in which the transformation from
  151.     3D to 2D is:
  152.  
  153.       (x,y,z) -> (i,j)
  154.         i = x / y
  155.         j = z / y
  156.  
  157.     In other words, you have to adjust P, M, and N to be relative
  158.     to the view, and if you have multiplications in your perspective
  159.     computation, you have to multiply the appropriate components of
  160.     P, M, and N to compute them.  Note that since typically in 3D
  161.     up is positive, and in 2D down is positive, there may be a
  162.     multiplication by -1 that needs to go into Py, My, Ny.  Note that
  163.     this also assumes that (0,0) in screen space is at the center
  164.     of the screen.  Since it's generally not, simply translate
  165.     your screen coordinates (i,j) as if they were before applying
  166.     the texture mapping math (or if you're funky you can modify
  167.     your viewspace to pre-skew them).
  168.  
  169.       For example, if your transforms are:
  170.          i = Hscale * x / y + Hcenter
  171.          j = -Vscale * z / y + Vcenter
  172.  
  173.       Then you should simply multiply Px, Mx, and Nx by Hscale,
  174.       and multiply Py, My, and Mz by -Vscale.  Then just remember
  175.       to subtract Hcenter and Vcenter from the i,j values right
  176.       before plugging them into the texture mapping equations.
  177.  
  178.     We begin by computing 9 numbers which are constant
  179.     for texture mapping the entire polygon (O stands for
  180.     Origin, H for Horizontal, and V for vertical; why I use
  181.     these names should become clear eventually):
  182.  
  183.          Oa = Nx*Pz - Nz*Px
  184.          Ha = Nz*Py - Ny*Pz
  185.          Va = Ny*Px - Nx*Py
  186.  
  187.          Ob = Mx*Pz - Mz*Px
  188.          Hb = Mz*Py - My*Pz
  189.          Vb = My*Px - Mx*Py
  190.  
  191.          Oc = Mz*Nx - Mx*Nz
  192.          Hc = My*Nz - Mz*Ny
  193.          Vc = Mx*Ny - My*Nx
  194.  
  195.     Ok.  Then, for a given screen location (i,j), the formula
  196.     for the texture space (u,v) coordinates corresponding to
  197.     it is:
  198.  
  199.          a = Oa + i*Ha + j*Va
  200.          b = Ob + i*Hb + j*Vb
  201.          c = Oc + i*Hc + j*Hc
  202.  
  203.          u = a/c
  204.          v = b/c
  205.  
  206.  
  207. TEXTURE
  208. TEXUE
  209. TXR   2.  The Basics
  210. X
  211.  
  212.     So you've got your polygon 3D engine running, and
  213. you'd like to start adding a bit of texture to your
  214. flat- or Gouraud-shaded polygons.  Well, it will make
  215. it look a lot cooler.  But let's point out the
  216. disadvantages of texture mapping right away:
  217.  
  218.       Slower
  219.       Sometimes hard to see polygon edges
  220.  
  221.     Each of these has certain ramifications on the
  222. overall approach you want to take with your code,
  223. which we'll come back to later, in sections 3 and 4.
  224.  
  225.     Practical advice: Don't try to get your riproaringly
  226. fast texture mapper running first.  Get a very simple,
  227. slow, "perfect" texture mapper working first, as described
  228. in the first subsection below.  This will allow you to make
  229. sure you've gotten the equations right.  Realize that I
  230. can't present the equations appropriate to every scenario,
  231. since there are simply too many spaces people can work
  232. in.  I've chosen to present the math from an extremely
  233. simple coordinate space which keeps the texture mapping
  234. relatively simple.  You'll have to work out the correct
  235. transformations to make it work right, and a slow but
  236. correct texture mapping routine may help you tweak the
  237. code as necessary to achieve this.  Use very simple
  238. polygons to start your testing; centered and facing the
  239. viewer should be your very first one (if done correctly,
  240. this will simply scale the texture).
  241.  
  242. TEX
  243. TE   Perfect Texture Mapping
  244. T
  245.  
  246.     To start with, we'll do slow but exact "perfect" texture
  247. mapping of a square tile with a simple texture map mapped onto
  248. it.  The polygon is defined in three-space using four points,
  249. and the texture map is 256x256 pixels.  Note that this is all
  250. talking about using floating point, so those of you working in
  251. C or Pascal are fine.  Those in assembly should realize that
  252. you have to do a bit of extra work to use fixed point, or you
  253. can beat out the floating point by hand if you want.
  254.  
  255.     First we have to "map" the texture onto the polygon.
  256. We have to define how the square texture map corresponds
  257. to the square polygon.  This is relatively simple.  Let
  258. one corner of the polygon be the origin (location <0,0>)
  259. of the texture map.  Let each of the other corners
  260. correspond to corners just off the edge of the texture
  261. map (locations <256, 0>, <256, 256>, and <0, 256>).
  262.  
  263.     We'd like to use the equations in section 1, which
  264. require vectors P, M, and N, where P is the origin,
  265. and M & N are the axes for u&v (which are _roughly_
  266. the coordinates in the texture map, but see below).
  267. In other words, P, M and N tells us where the texture
  268. lies relative to the polygon.  P is the coordinate in
  269. three-space where the origin of the texture is.  M
  270. tells us which way the "horizontal" dimension of the
  271. texture lies in three-space, and N the "vertical".
  272.  
  273.     Suppose the polygon has four vertices V[0], V[1],
  274. V[2], and V[3] (all four of these are vectors).  Then,
  275. P is simply V[0].  M is a vector going from the origin
  276. to the corner <256, 0>, so M is a vector from V[0] to
  277. V[1], so M = V[1] - V[0].  N is a vector from the origin
  278. to the corner <0, 256>, so N is V[3] - v[0].
  279.  
  280.     P = V[0]
  281.     M = V[1] - V[0]    { note these are vector subtractions }
  282.     N = V[3] - V[0]
  283.  
  284.     Again, remember that we need P, M, and N in the viewspace
  285. discussed with the equation, so make sure you've transformed
  286. the Vs appropriately, or you can compute P, M, and N in world
  287. or object space and transform them into viewspace.
  288.  
  289.     Compute the 9 magic numbers (vectors O, H, and V) as described
  290. in section 1.  Now, take your 3D polygon and process it as normal.
  291. Scan convert it so that you have a collection of rows of pixels
  292. to process.
  293.  
  294.     Now, iterate across each row.  For each pixel in the polygon
  295. whose screen coordinates are <i,j>, apply the rest of the math
  296. described in section 1; that is, compute a, b, and c, and from
  297. them compute <u,v>.
  298.  
  299.     I said before that <u,v> are basically the texture map
  300. coordinates.  What they are in truth are the coordinates in texture
  301. map space.  Because of the way we defined texture map space,
  302. we'll actually find that u and v both run from 0..1, not 0..256.
  303. This is an advantage for descriptive purposes because u and v
  304. are always 0 to 1, regardless of the size of the texture map.
  305.  
  306.     So, to convert u&v to pixelmap coordinates, multiply them
  307. both by 256.  Now, use them as indices into the texture map,
  308. output the value found there, and voila, you've texture mapped!
  309.  
  310. The loop should look something like this:
  311.  
  312. [ loop #1 ]
  313.     for every j which is a row in the polygon
  314.       screen = 0xA0000000 + 320*j
  315.       for i = start_x to end_x for this row
  316.         a = Oa + (Ha * i) + (Va * j)
  317.         b = Ob + (Hb * i) + (Vb * j)
  318.         c = Oc + (Hc * i) + (Vc * j)
  319.         u = 256 * a / c
  320.         v = 256 * b / c
  321.         screen[i] = texture_map[v][u]
  322.       endfor
  323.     endfor
  324.  
  325.     Once you've got that working, congratulations!  You're
  326. done dealing with the annoying messy part, which is getting
  327. those 9 magic numbers computed right.  The rest of this is
  328. just hard grunt work and trickery trying to make the code
  329. faster.
  330.  
  331.     From here on in, I'm only going to look at the inner
  332. loop, that is, a single run (row or column), and let the
  333. rest of the runs be understood.
  334.  
  335. TEX
  336. TE   Prepare to meet thy DOOM
  337. T
  338.  
  339.     This subsection is concerned with vastly speeding
  340. up texture mapping by restricting the mapper to walls,
  341. floors and ceilings, or what is commonly called
  342. DOOM-style texture mapping, although it of course predates
  343. DOOM (e.g. Ultima Underworld [*], Legends of Valour).
  344.  
  345. [*  Yes, Underworld allowed you to tilt the view,
  346. but it distorted badly.  Underworld essentially used
  347. DOOM-style tmapping, and tried to just use that on
  348. arbitrarily-angled polygons.  I can't even begin to
  349. guess what Underworld II was doing for the same thing.]
  350.  
  351.     To begin with, let's take loop #1 and get as much
  352. stuff out of the inner loop as we can, so we can see
  353. what's going on.  Note that I'm not going to do low-level
  354. optimizations, just mathematical optimizations; I assume
  355. you understand that array walks can be turned into
  356. pointer walks, etc.
  357.  
  358. [ loop #2 ]
  359.       a = Oa + Va*j
  360.       b = Ob + Vb*j
  361.       c = Oc + Vc*j
  362.  
  363.       a += start_x * Ha
  364.       b += start_x * Hb
  365.       c += start_x * Hc
  366.  
  367.       for i = start_x to end_x
  368.         u = 256 * a / c
  369.         v = 256 * b / c
  370.         screen[i] = texture_map[v][u]
  371.         a += Ha
  372.         b += Hb
  373.         c += Hc
  374.       endfor
  375.  
  376.     With fixed point math, the multiplies by 256
  377. are really just shifts.  Furthermore, they can be
  378. "premultiplied" into a and b (and Ha and Hb) outside
  379. the loop.
  380.  
  381.     Ok, so what do we have left?  Three integer adds,
  382. a texture map lookup, and two extremely expensive fixed-point
  383. divides.  How can we get rid of the divides?
  384.  
  385.     This is the big question in texture mapping, and most
  386. answers to it are _approximate_.  They give results that
  387. are not quite the same as the above loop, but are difficult
  388. for the eye to tell the difference.
  389.  
  390.     However, before we delve into these, there's a very
  391. special case in which we can get rid of the divides.
  392.  
  393.     We can move the divide by c out of the loop without
  394. changing the results IFF c is constant for the duration
  395. of the loop.  This is true if Hc is 0.  It turns out that
  396. Hc is 0 if all the points on the run of pixels are the same
  397. depth from the viewer, that is, they lie on a line of
  398. so-called "constant Z" (I would call it "constant Y" in
  399. my coordinate system).
  400.  
  401.     The requirement that a horizontal line of pixels be the
  402. same depth turns out to be met by ceilings and floors.
  403. For ceilings and floors, Hc is 0, and so the loop can be
  404. adjusted to:
  405.  
  406. [ loop #3 ]
  407.       __setup from loop #2__
  408.       u = 256 * a / c
  409.       v = 256 * b / c
  410.       du = 256 * Ha / c
  411.       dv = 256 * Hb / c
  412.       for i = start_x to end_x
  413.         screen[i] = texture_map[v][u]
  414.         u += du
  415.         v += dv
  416.      endfor
  417.  
  418.     Now _that_ can be a very fast loop, although adjusting
  419. the u&v values so they can be used as indices has been
  420. glossed over.  I'll give some sample assembly in the next
  421. section and make it all explicit.
  422.  
  423.     First, though, let's look at walls.  Walls are almost
  424. identical to floors and ceilings.  However, with walls,
  425. Vc is 0, instead of Hc.  This means that to write a loop
  426. in which c is constant, we have to walk down columns instead
  427. of across rows.  This affects scan-conversion, of course.
  428.  
  429.     The other thing about walls is that with floors, since
  430. you can rotate about the vertical axis (Z axis for me, Y axis
  431. for most of you), the horizontal runs on the floors cut
  432. across the texture at arbitrary angles.  Since you're
  433. bound to not tilt your head up or down, and since the
  434. polygons themselves aren't tilted, you generally find
  435. that for walls, Va is 0 as well.  In other words, as you
  436. walk down a column of a wall texture, both a & c are constant,
  437. so u is constant; you generally only change one coordinate,
  438. v, in the texture map.  This means the inner loop only needs
  439. to update one variable, and can be made to run _very_ fast.
  440.  
  441.     The only thing missing from this discussion for creating
  442. a DOOM clone is how to do transparent walls, how to do
  443. lighting things, and how to make it fast enough.  These will
  444. be discussed in section 4, although the some of the speed
  445. issue is addressed by the inner loops in the next subsection,
  446. and the rest of the speed issue is discussed in general terms
  447. in section 3.
  448.  
  449. TEX
  450. TE   ...wrapped around your finger...
  451. T
  452.  
  453.     So far, we've only looked at texture mapping a single
  454. polygon.  Of course, it's obvious how to texture map
  455. a lot of polygons--just lather, rinse, repeat.  But it
  456. may seem sort of wasteful to go through all the 3D math
  457. and all over and over again if we just want to have one
  458. long wall with the same texture repeating over and over
  459. again--like linoleum tiles or wallpaper.
  460.  
  461.     Well, we don't have to.  Let's think about this
  462. idea of a "texture map space" some more.  We defined
  463. it as being a coordinate system "superimposed" on
  464. the polygon that told us where the texture goes.
  465. However, when we implemented it, we simply used the
  466. polygon itself (in essence) as the coordinate space.
  467.  
  468.     To see this, make a polygon which is a rectangle,
  469. perhaps four times as long as it is tall.  When it
  470. is drawn, you will see the texture is distorted,
  471. stretched out to four times its length in one dimension.
  472. Suppose we wanted it to repeat four times instead?
  473.  
  474.     The first step is to look at what the definition
  475. of the texture map space means.  The texture map space
  476. shows how the physical pixelmap itself goes onto the
  477. polygon.  To get a repeating texture map, our first
  478. step is to just get one of the copies right.  If
  479. we set up our P,M, & N so that the M only goes one
  480. quarter of the way along the long edge of the
  481. rectangle, we'll map the texture onto just that
  482. quarter of the rectangle.
  483.  
  484.      Here's a picture to explain it:
  485.  
  486.               Polygon A-B-C-D                    Texture map
  487.  
  488.    A          E                                  u=0     u=1
  489.     o--------o-___________          B        v=0  11112222
  490.     |111112222            ---------o              11112222
  491.     |111112222                     |              33334444
  492.     |111112222                     |              33334444
  493.     |333334444                     |         v=1
  494.     |333334444                     |
  495.     |333334444 ___________---------o
  496.     o--------o-                     C
  497.    D          F
  498.  
  499.     So, we used to map (u,v)=(0,0) to A, (u,v)=(1,0) to B, and
  500. (u,v) = (0,1) to D.  This stretched the texture map out to
  501. fill the entire polygon map.
  502.  
  503.     Now, instead, we map (u,v)=(1,0) to E.  In other words,
  504. let P = A, M = E-A, and N = D-A.  In this new coordinate
  505. space, we will map the texture onto the first quarter of
  506. the polygon.
  507.  
  508.     What about the rest of the polygon?  Well, it simply
  509. turns out that for the first quarter 0 <= u <= 1.  For
  510. the rest, 1 <= u <= 4.
  511.  
  512.     To make the texture wrap around, all we have to do is
  513. ignore the integer part of u, and look at the fractional part.
  514. Thus, as u goes from 1 to 2, we lookup in the texture map using
  515. the fractional part of u, or (u-1).
  516.  
  517.     This is all very simple, and the upshot is that, once
  518. you define P, M, and N correctly, you simply have to mask
  519. your fixed-point u&v values; this is why we generally use
  520. texture maps whose sides are powers of two, so that we can
  521. mask to stay within the texture map.  (Also because they
  522. fit conveniently into segments this way, and also so that
  523. the multiply to convert u&v values from 0..1 to indices
  524. is just a shift.)
  525.  
  526.     I'm assuming that's a sufficient explanation of the
  527. idea for you to get it all setup.  So here's the assembly
  528. inner loops I promised.  I'm not going to bother giving
  529. the ultra-fast vertical wall-drawing case, just the
  530. horizontal floor/ceiling-drawing case.
  531.  
  532.     Note that a mask of 255 (i.e. for a 256x256 texture)
  533. can be gotten for free; however, no program that I'm
  534. aware of uses texture maps that large, since they
  535. simply require too much storage, and they can cache very
  536. poorly in the internal cache.
  537.  
  538.     First, here's your basic floor/ceiling texture mapper,
  539. in C, with wraparound, and explicitly using fixed point
  540. math--but no setup.
  541.  
  542. [ loop #4 ]
  543.     mask = 127, 63, 31, 15, whatever.
  544.     for (i=0; i < len; ++i) {
  545.       temp = table[(v >> 16) & mask][(u >> 16) & mask];
  546.       u += du;
  547.       v += dv;
  548.     }
  549.  
  550.     Now, here's an assembly one.  This one avoids the
  551. shifts and does both masks at the same time, and uses
  552. 16 bits of "fractional" precision and however many bits
  553. are needed for the coordinates.  Note that I assume
  554. that the texture, even if it is 64x64, still has each
  555. row starting 256 bytes apart.  This just requires some
  556. creative storage approaches, and is crucial for a fast
  557. inner loop, since no shifting&masking is required to
  558. assemble the index.
  559.  
  560.        mov  al,mask
  561.        mov  ah,mask
  562.        mov  mask2,ax      ; setup mask to do both at the same time
  563.  
  564. loop5  and  bx,mask2      ; mask both coordinates
  565.        mov  al,[bx]       ; fetch from the texture map
  566.        stosb
  567.        add  dx,ha_low     ; update fraction part of u
  568.        adc  bl,ha_hi      ; update index part of u
  569.        add  si,hb_low     ;   these are constant for the loop
  570.        adc  bh,hb_hi      ;   they should be on the stack
  571.        loop loop5         ;   so that ds is free for the texture map
  572.  
  573.     This code is decent, but nowhere near as fast as it can be.
  574. The main trick to improving performance is to use 32 bit adds
  575. instead of two adds.  The problem with this is that extra operations
  576. are required to setup the indexing into the texture map.  Through
  577. the use of the ADC trick, these can be minimized.  In the following
  578. code, bl and bh are unchanged.  However, the top half of EBX now
  579. contains what used to be in si, and the other values have been
  580. moved into registers.  ESI contains hb_low in the top half, and
  581. ha_hi in the low 8 bits.  This means that ADC EBX,ESI achieves
  582. the result of two of the additions above.  Also, we start using
  583. BP, so we move our variables into the data segment and the texture
  584. map into FS.
  585.  
  586. loop6  and  bx,mask2
  587.        mov  al,fs:[bx]
  588.        stosb
  589.        add  dx,bp          ; update fractional part of u
  590.        adc  ebx,esi        ; update u (BL) and frac. part of v (EBX)
  591.        adc  bh,ch          ; update index part of v
  592.        dec  cl
  593.        jnz  loop6
  594.  
  595.     This is a bit faster, although it has one bug.  It's
  596. possible for the addition into BL to overflow into BH.  It might
  597. not seem to be, since BL is masked every iteration back down to
  598. stay in 0..127, 0..63, or whatever.  However, if the step is
  599. negative, then BL will be decremented each iteration, and may
  600. "underflow" and subtract one from BH.  To handle this, you need
  601. a seperate version of the loop for those cases.
  602.  
  603.     If you're not doing wraparound textures, you can speed the
  604. loop up a bit more by removing the and.  You can run entirely
  605. from registers except for the texture map lookup.  Additionally,
  606. unrolling the loop once cuts down on loop overhead, and is crucial
  607. if you're writing straight to the VGA, since it doubles your
  608. throughput to a 16-bit VGA card.
  609.  
  610.     Here's a very fast no-wraparound texture mapper.  It uses
  611. the ADC trick twice.  Note that the carry flag is maintained
  612. around the loop every iteration; unfortunately the 'and' required
  613. for wraparound textures clears the carry flag (uselessly).  EBX
  614. and EDX contain u & v in their bottom 8 bits, and contain the
  615. fractional parts of v & u in their top bits (note they keep the
  616. _other_ coordinate's fractional parts).  You have to have prepped
  617. the carry flag first; if you can't figure this technique out, don't
  618. sweat it, or look to see if someone else has a more clear discussion
  619. of how to do fast fixed-point walks using 32-bit registers.
  620.  
  621.     This loop is longer because it does two pixels at a time.
  622.  
  623. loop7   mov  al,[bx]       ; get first sample
  624.         adc  edx,esi       ; update v-high and u-low
  625.         adc  ebx,ebp       ; update u-high and v-low
  626.         mov  bh,dl         ; move v-high into tmap lookup register
  627.         mov  ah,[bx]       ; get second sample
  628.         adc  edx,esi
  629.         adc  ebx,ebp
  630.         mov  bh,dl
  631.         mov  es:[di],ax    ; output both pixels
  632.         inc  di            ; add 2 to di without disturbing carry
  633.         inc  di
  634.         dec  cx
  635.         jnz  loop7
  636.  
  637.     I went ahead and 486-optimized the stosw/loop at the end to make
  638. cycle-counting easier.  All of these instructions are single cycle
  639. instructions, except the branch, and the segment-override.  So you're
  640. looking at roughly 15 cycles for every two pixels.  Your caching
  641. behavior on the reads and writes will determine the actual speed.
  642. It can be unrolled another time to further reduce the loop overhead;
  643. the core operations are 9 instructions (10 cycles) for every two
  644. pixels.  Note the "inc di/inc di", which protects the carry flag.
  645. If you unroll it again, four "inc di"s will be required.  Unroll it
  646. another time, and you're better off saving the carry flag, adding,
  647. and restoring, for example "adc ax,ax/add di,8/shr ax,1", rather
  648. than 8 "inc di"s.
  649.  
  650. TEX
  651. TE   Lost My Shape (trying to act casual)
  652. T
  653.  
  654.     Non-rectangular polygons are trivial under this system.
  655. Some approaches require you to specify the (u,v) coordinates
  656. for each of the vertices of the polygon.  With this technique,
  657. you instead specify the 3D coordinates for three of the
  658. "vertices" of the texture map.  So the easiest way of handling
  659. a texture of a complex polygon is simply to use a square
  660. texture which is larger than the polygon.  For example:
  661.  
  662.    P1                       P2
  663.      x    B  _______  C    x
  664.            /         \
  665.          /             \
  666.      A /                 \
  667.        \                 / D
  668.          \             /
  669.            \ _______ /
  670.      x    F           E
  671.    P3
  672.  
  673.     Now, we simply define the texture map such that P is P1,
  674. M is P2-P1, and N is P3-P1.  Then, if our texture looks like
  675. this:
  676.  
  677.      u=0        u=1
  678.       ------------
  679.  v=0 |..XXoooooo..
  680.      |.XXXXoooooo.
  681.      |XXXXXXoooooo
  682.      |.XXXXXXoooo.
  683.  v=1 |..XXXXXXoo..
  684.  
  685.     Then the regions marked by '.' in the texture map will
  686. simply never be displayed anywhere on the polygon.
  687.  
  688.     Wraparound textures can still be used as per normal,
  689. and concave polygons require no special handling either.
  690.  
  691.     Also, you can get special effects by having M and N not
  692. be perpendicular to each other.
  693.  
  694. TEXTURE
  695. TEXUE
  696. TXR   3.  The Hazards
  697. X
  698.  
  699.     This sections discusses some of the pitfalls and
  700. things-aren't-quite-as-simple-as-they-sounded issues
  701. that come up while texture mapping.  All of the
  702. information is, to some extent, important, whether
  703. you've encountered this problem or not.
  704.  
  705. TEX
  706. TE   Cl-cl-cl-close to the Edge
  707. T
  708.  
  709.     At some time when you're texture mapping, you'll
  710. discover (perhaps from the screen, perhaps from a
  711. debugger) that your U & V values aren't within the
  712. 0..1 range; they'll be just outside it.
  713.  
  714.     This is one of these "argh" problems.  It is
  715. possible through very very careful definition of
  716. scan-conversion operations to avoid it, but you're
  717. likely to encounter it.
  718.  
  719.     If you use wraparound textures, you may not ever
  720. notice it, however, since when it happens, the
  721. texture will simply wraparound and display an
  722. appropriate pixel.
  723.  
  724.     If not, you may get a black pixel, or just
  725. garbage.  It'll only happen at the edges of your
  726. polygon.
  727.  
  728.     The reason this happens is because your scan-conversion
  729. algorithm may generate pixels "in the polygon" whose
  730. pixel-centers (or corners, depending on how you've
  731. defined it) are just outside the texture--that is,
  732. they're outside the polygon itself.
  733.  
  734.     The right solution to this is to fix your scan-converter.
  735. If your texture mapper computes u&v coordinates based on
  736. the top-left corner of the pixel (as the one I've defined
  737. so far has), make sure your scan-converter only generates
  738. pixels whose top-left corner is really within the polygon.
  739. If you do this, you may need to make a minor change to my
  740. definition of M & N, but I'm not going to discuss this
  741. further, since you probably won't do this.
  742.  
  743.     A second option is to define P, M, and N such that the
  744. texture map space is slightly bigger than the polygon; that
  745. is, so that if you go just off the edge of the polygon,
  746. you'll still be within the texture map.
  747.  
  748.     This is a pain since you end up having to transform extra
  749. 3D points to do it.
  750.  
  751.     The third, and probably most common solution, is to always
  752. use wraparound textures, which hide the problem, but prevent
  753. you from using textures that have one edge that highly contrasts
  754. with another.
  755.  
  756.     The fourth, and probably second most common solution,
  757. and the one that turns out to be a real pain, is to "clamp"
  758. the u&v values to be within the texture all the time.
  759.  
  760.     Naively, you just put this in your inner loop:
  761.  
  762.       if (u < 0) u = 0; else if (u > 1) u = 1;
  763.       if (v < 0) v = 0; else if (v > 1) v = 1;
  764.  
  765.     Of course, you don't really do this, since it'd slow
  766. you down far too much.  You can do this outside the loop,
  767. clamping your starting location for each run.  However,
  768. you can't, under this system, clamp the ending value
  769. easily.
  770.  
  771.     Remember that in the loop we update u and v with
  772. (essentially) Ha/c and Hb/c.  These are constant
  773. across the entire run, but not constant across the
  774. entire polygon, because c has different values for
  775. different runs.
  776.  
  777.     We can compute du and dv in a different way to
  778. allow for clamping.  What we do is we explicitly compute
  779. (a,b,c) at (start_x, j) as we did before, but we also
  780. compute (a,b,c) at (end_x, j).  From these we compute
  781. (u,v) at start_x & at end_x.  Next we clamp both sets
  782. of u & v.  Then we compute du and dv with
  783.  
  784.     du = (u2 - u1) / (end_x - start_x - 1)
  785.     dv = (v2 - v1) / (end_x - start_x - 1)
  786.  
  787.     This is slightly more expensive than the old way,
  788. because we have to compute u2 and v2, which requires
  789. extra divides.  However, for methods that explicitly
  790. calculate u&v sets and then compute deltas (and we'll
  791. see some in section 4), this is the way to go.
  792.  
  793.     One final thing you can do is interpolate the (a,b,c)
  794. triple from the vertices as you scan convert.  This will
  795. guarantee that all (a,b,c) triples computed will lie be
  796. within the polygon, and no clamping will be necessary
  797. (but deltas must still be computed as above).  However,
  798. you have to make sure the (a,b,c) values you compute at
  799. the vertices are clamped themselves, which is not too hard
  800. by a bit more complicated than clamping (u,v) values.
  801.  
  802. TEX
  803. TE   Out of This Domain -- Zero's Paradox
  804. T
  805.  
  806.     Divides by zero are ugly.  We programmers don't
  807. like them.  If this were an ideal world (a quick
  808. nod to mathematicians and some physicists), the
  809. texture mapping equations would be divide-by-zero-free.
  810.  
  811.     Unfortunately, it's a repercussion of the exact
  812. same problem as above that you can bump into them.
  813.  
  814.     Remember above, I noted that it's possible to
  815. get (u,v) pairs with a value just outside of the
  816. 0..1 range, because a pixel we're texture mapping
  817. isn't even in the polygon?
  818.  
  819.     Well, even worse, it's possible for this pixel,
  820. which isn't in the polygon, to be along the horizon
  821. line (vanishing point) for the polygon.  If this
  822. happens, your Y value (sorry, Z for most of you)
  823. would be infinite if you tried to compute the 3D
  824. coordinates from the screen coordinates; and in
  825. the (u,v) computation, you end up with a 0 value
  826. for c.  Since u = a/c, blammo, divide by 0.
  827.  
  828.     Well, the solution is simple.  Test if c is 0,
  829. and if it is, don't divide.  But what _should_ you do?
  830.  
  831.     Well, let's look at an "even worse" case.  Suppose
  832. the pixel is so far off the polygon it's across the
  833. horizon line.  In this case, we'll end up with c having
  834. the "wrong" sign, and while our divide won't fault on
  835. us, our u&v values will be bogus.
  836.  
  837.     What do we do then?
  838.  
  839.     We can't clamp our a&b&c values very easily.
  840. Fortunately, it turns out we don't have to.  If
  841. this happens, it means the edge of the polygon
  842. must be very close to the horizon, or the viewer
  843. must be very, very flat to the polygon (if you know
  844. what I mean).  If so, the viewer can't really tell
  845. what should be "right" for the polygon, so if we
  846. screw up the u&v values, it really doesn't matter.
  847.  
  848.     So the answer is, don't worry if c gets the
  849. wrong sign, and if c comes out to be 0, use any
  850. value for u&v that you like--(0,0) makes an obvious
  851. choice.
  852.  
  853.     I've never had a serious problem with this, but
  854. it is possible that this could actually give you some
  855. pretty ugly results, if, say, two corners of a polygon
  856. both "blew up", and you treated them both as being
  857. (0,0).  It can also cause problems with wraparound
  858. polygons not repeating the right amount.
  859.  
  860. TEX
  861. TE   Do the Dog
  862. T
  863.  
  864.     Most polygon 3D graphics engines probably use
  865. the painter's algorithm for hidden surface removal.
  866. You somehow figure out what order to paint the polygons
  867. in (depth sort, BSP trees, whatever), and then paint
  868. them back-to-front.  The nearer polygons obscure the
  869. farther ones, and voila!, you're done.
  870.  
  871.     This works great, especially in a space combat
  872. simulator, where it's rare that you paint lots of pixels.
  873.  
  874.     You can texture map this way, too.  For example,
  875. Wing Commander II doesn't texture map, but it does
  876. real time rotation, which involves essentially the
  877. same inner loop.  Wing Commander II is fast--until
  878. a lot of ships are on the screen close to you, at which
  879. point it bogs down a bit.
  880.  
  881.     If you care about not slowing down too much in the above
  882. case, or you want to do an "indoor" renderer with lots of
  883. hidden surfaces, you'll find that with texture mapping,
  884. you can ill-afford to use the painter's algorithm.
  885.  
  886.     You pay a noticeable cost for every pixel you texture
  887. map.  If you end up hiding 80% of your surfaces (i.e. there
  888. are five "layers" everywhere on the screen), you end up
  889. "wasting" 80% of the time you spend on texture mapping.
  890.  
  891.     To prevent this, you have to use more complex methods
  892. of hidden surface removal.  These will probably slow you down
  893. somewhat, but you should make up for it with the gain in texture
  894. mapping.
  895.  
  896.     The essential idea is to only texture map each screen pixel once.
  897. To do this, you do some sort of "front-to-back" painting, where
  898. you draw the nearest surface first.  Any pixel touched by this
  899. surface should never be considered for drawing again.
  900.  
  901.     There are many ways to do this.  You can process a single
  902. scanline or column at a time and use ray-casting or just
  903. "scanline processing", then resolve the overlap between the
  904. runs with whatever method is appropriate.  You can stay
  905. polygonal and maintain "2D clipping" information (a data
  906. structure which tracks which pixels have been drawn so far).
  907.  
  908.     Beyond getting a fast inner loop for texture mapping,
  909. getting a fast hidden-surface-removal technique (and a fast
  910. depth-sorting technique if appropriate) is probably the
  911. next most crucial thing for your frame rate.
  912.  
  913.     But the details are beyond the scope of this article.
  914.  
  915.     Note that if you attempt to use a Z-buffer, you will
  916. still end up paying all of the costs of texture mapping for
  917. every forward-facing polygon (or at least 50% of them if
  918. you get really tricky; if you get really, really tricky,
  919. the sky's the limit.)  I strongly doubt that any PC game
  920. now out, or that will come out in the next year, will
  921. render full-screen texture mapping through a Z-buffer.
  922. (Superimposing a rendered image on a Z-buffered background
  923. is a different issue and is no doubt done all the time.)
  924.  
  925.  
  926. TEXTURE
  927. TEXUE
  928. TXR   4.  The Complexities
  929. X
  930.  
  931.     In this section we will discuss lots of miscellaneous
  932. topics.  We'll look at some more optimizations, such as
  933. considerations for dealing with slow VGA cards, and how to
  934. texture map arbitrarily-angled polygons without doing two
  935. divides per pixel.  We'll talk about a technique that lets
  936. you use textures with high-frequency components, and one way
  937. to integrate lighting into texture-mapping.
  938.  
  939. TEX
  940. TE   Arbitrarily-Angled Polygons
  941. T
  942.  
  943.     First suggestion: Don't.  Set up your world to
  944. have all (or mostly) walls and floors.  Supporting
  945. arbitrarily-angled polygons is going to slow you
  946. down, no matter what.
  947.  
  948.     The original texture mapping loop, which supported
  949. arbitrarily-angled polygons, required two divides per
  950. pixel.  We don't have to go that slow, but we'll never
  951. go as fast as DOOM-style rendering can go.  (However,
  952. as you start to use more sophisticated lighting algorithms
  953. in your inner loop, the cost of handling arbitrarily-
  954. angled polygons may start to become less important.)
  955.  
  956.     There is one way to texture map such polygons
  957. "perfectly" without two divides per pixel, and a
  958. host of ways to do it "imperfectly".  I'll discuss
  959. several of these ways in varying amounts of detail.
  960. Your best bet is to implement them all and see
  961. which ones you can get to run the fastest but still
  962. look good.  You might find that one is faster for
  963. some cases but not for others.  You could actually
  964. have an engine which uses all the methods, depending
  965. on the polygon it's considering and perhaps a "detail"
  966. setting which controls how accurate the approximations
  967. are.
  968.  
  969.     The "perfect" texture mapping algorithm is
  970. described in another article, "Free-direction texture
  971. mapping".  I'll summarize the basic idea and the
  972. main flaw.  The basic idea is this.  For ceilings
  973. and walls, we were able to walk along a line on
  974. the screen for which the step in the "c" parameter
  975. was 0; this was a line of "constant Z" on the
  976. polygon.
  977.  
  978.     It turns out that every polygon has lines of
  979. "constant Z"--however, they can be at any angle,
  980. not necessarily vertical or horizontal.
  981.  
  982.     What this means, though, is that if you walk
  983. along those lines instead of walking along a horizontal
  984. or vertical, you do not need a divide to compute your
  985. texture map coordinates, just deltas.
  986.  
  987.     The details can be found in the other article.
  988. The slope of the line to walk on the screen is something
  989. like Hc/Vc.
  990.  
  991.     Note, however, that the "DOOM" approach was _just_
  992. an optimization for a special case.  The wall & ceiling
  993. renderers produce exactly the same results as a
  994. perfect texture mapper, for the polygons that they
  995. handle (ignoring rounding errors and fixed-point precision
  996. effects).  This is not true for the "free-direction"
  997. texture mapper.  While there is a line across the
  998. screen for which the polygon has constant Z,
  999. you cannot walk exactly along that line, since you
  1000. must step by pixels.  The end result is that while
  1001. in the texture map space, you move by even steps,
  1002. in the screen space, you move with ragged jumps.
  1003. With perfect texture mapping, you always sample
  1004. from the texture map from the position corresponding
  1005. to the top-left/center of each pixel.  With the
  1006. free-direction mapper, you sample from a "random"
  1007. location within the pixel, depending on how you're
  1008. stepping across the screen.  This "random" displacement
  1009. is extremely systematic, and leads to a systematic
  1010. distortion of the texture.  I find it visually
  1011. unacceptable with high-contrast textures, compared to
  1012. perfect texture mapping, but you should try it and decide
  1013. for yourself.  The technically inclined should note that
  1014. this is simply the normal "floor" renderer with an extra
  1015. 2D skew, and that while 2D skews are trivial, they are
  1016. non-exact and suffer from the flaw described above.
  1017.  
  1018.     The only other alternative for arbitrarily-angled
  1019. polygons is to use some kind of approximation.  We
  1020. can characterize u and v as functions of i (the horizontal
  1021. screen position; or use 'j' if you wish to draw columns);
  1022. for instance, u = a / c, where a = q + i*Ha, c = p + i*Hc.
  1023. So we can say  u(i) = (q + i*Ha) / (r + i*Hc).
  1024.  
  1025.     Now, instead of computing u(i) exactly for each i,
  1026. as we've done until now, we can instead compute some
  1027. function u'(i) which is approximately equal to u(i) and
  1028. which can be computed much faster.
  1029.  
  1030.     There are two straightforward functions which we
  1031. can compute very fast.  One is the simple linear
  1032. function we used for DOOM-style mapping, u'(x) = r + x*s.
  1033. Since the function we're approximating is curved (a
  1034. hyperbola), a curved function is another possibility,
  1035. such as u'(x) = r + x*s + x^2*t.  (SGI's Reality Engine
  1036. apparently uses a cubic polynomial.)
  1037.  
  1038.     If you try both of these approximations on a very
  1039. large polygon at a sharp angle, you will find that
  1040. they're not very good, and still cause visible
  1041. curvature.  They are, of course, only approximations.
  1042. The approximations can be improved with a simple
  1043. speed/quality trade-off through subdivision.  The
  1044. idea of subdivision is that the approximation is
  1045. always of high quality for a small enough region,
  1046. so you can simply subdivide each region until the
  1047. subregions are small enough to have the desired
  1048. quality.
  1049.  
  1050.     There are two ways to subdivide.  One simple way
  1051. is to subdivide the entire polygon into smaller
  1052. polygons.  This should be done on the fly, not
  1053. ahead of time, because only polygons that are at
  1054. "bad" angles need a lot of subdivision.  After
  1055. dividing a polygon into multiple smaller ones,
  1056. render each one seperately.  Use the original
  1057. P, M, and N values for all of the new polygons
  1058. to make the texture remain where it should be
  1059. after subdivision.
  1060.  
  1061.     The (probably) better way to subdivide is to
  1062. subdivide runs instead of polygons, and so I'll
  1063. discuss this in more detail.  The essential thing
  1064. is that to do an approximation, you evaluate the
  1065. original function at two or more locations and
  1066. then fit your approximate function to the computed
  1067. values.  One advantage of run subdivision is that
  1068. you can share points that you evaluated for one
  1069. subrun with those of the next.
  1070.  
  1071.     First lets turn back to the two approximations
  1072. under consideration.  The first is what is called
  1073. "bilinear texture mapping", because the function
  1074. is linear and we're tracking two ("bi") values.
  1075. To use this method, we compute the function at
  1076. both endpoints: u1 = u(start_x), u2 = u(end_x).
  1077. Then we compute our start and step values.  To
  1078. keep things simple, I'm going to assume the approximation
  1079. function u'(x) is defined from 0..end_x-start_x, not
  1080. from start_x..end_x.
  1081.  
  1082.     So, the linear function u'(x) = r + s*x, where
  1083. u'(0) = u1 and u'(end_x - start_x) = u2 is met by
  1084. letting r = u1, s = (u2 - u1) / (end_x - startx).
  1085.  
  1086.     Now, suppose our run goes from x = 10 to x = 70.
  1087. If we evaluate u(10), u(20), u(30), u(40),... u(70),
  1088. then we can have six seperate sections of bilinear
  1089. texture mapping.
  1090.  
  1091.     For a quadratic, there are several ways to compute
  1092. it.  One way is to compute an additional sample in the
  1093. middle; u3 = u((start_x + end_x)/2).  Then we can
  1094. fit u1,u2, and u3 to u'(x) = r + s*x + t*x^2 with:
  1095.  
  1096.    len = (end_x - start_x)
  1097.    k   = u1 + u2 - u3*2
  1098.    r   = u1
  1099.    s   = (u2 - u1 - 2*k)/len
  1100.    t   = 2*k / len^2
  1101.  
  1102.     Note that to use this in code, you cannot simply
  1103. use a loop like this:
  1104.  
  1105.    r += s;
  1106.    s += t;
  1107.  
  1108.     because the r,s, and t values aren't correct
  1109. for discrete advancement.  To make them correct,
  1110. do this during the setup code:
  1111.  
  1112.     R = r
  1113.     S = s + t
  1114.     T = 2*t
  1115.  
  1116.     Then the loop of (...use R..., R += S, S += T) will work correctly.
  1117.  
  1118.     The biquadratic loop will be slower than the linear
  1119. loop, but will look better with fewer subdivisions.  You
  1120. can share one of the endpoints from one biquadratic
  1121. section to the next.  Note, though, that you require twice
  1122. as many calculations of u&v values for the same number of
  1123. subdivisions with a biquadratic vs. a bilinear.
  1124.  
  1125.     Another thing to do is to choose how to subdivide the run
  1126. more carefully.  If you simply divide it in half or into quarters,
  1127. you'll discover that some of the subruns come out looking better
  1128. than others.  So there are some things you can do to improve the
  1129. subdivision system.  Another thing you can do is to try to make
  1130. most of your subruns have lengths which are powers of two.  This
  1131. will let you use shifts instead of divides when computing r,s, and
  1132. t, which cuts down on your overhead, which lets you use more
  1133. subdivisions and get the same speed.
  1134.  
  1135.     Note something very important.  Subdivision increases the
  1136. overhead per run; biquadratic and other things increase the
  1137. cost of the inner loop.  Before you go crazy trying to optimize
  1138. your arbitrarily-angled polygon renderer, make sure you're
  1139. rendering some "typical" scenes.  The "right" answer is going
  1140. to depend on whether you have lots of very shorts runs or
  1141. fewer, longer runs.  If you optimize based on a simple test
  1142. case, you may end up suboptimal on the final code.
  1143.  
  1144.     You probably still want to have both a column-based and a
  1145. row-based renderer, and use whichever one the polygon is
  1146. "closer to" (e.g. if Hc is closer to 0 than Vc, use the row-based).
  1147. Note that the free-direction renderer looks its worst (to me)
  1148. for very small rotations, i.e. when Hc or Vc are very close to 0.
  1149. Since in these cases not much subdivision is needed, even if you
  1150. choose to use a free-direction mapper as your primary renderer,
  1151. you might still want to have "almost wall" and "almost floor"
  1152. renderers as well.
  1153.  
  1154.     Finally, there is one more approximation method you can use,
  1155. which is faster than any of the ones discussed so far, but is
  1156. simply totally and utterly wrong.  This is the approach used
  1157. by Michael Abrash in his graphics column in Dr. Dobbs.  While
  1158. it's quite wrong, it works on polygons which are entirely
  1159. constant Y (sorry, Z), and can be a noticeable speedup.
  1160.  
  1161.     What you do is 2D (instead of 3D) interpolation.  You mark
  1162. each vertex with its coordinates in the texture map.  Then when
  1163. you scan convert, you interpolate these values between vertices
  1164. on the edges of your runs.  Thus, scan conversion will generate
  1165. runs with (u,v) values for the left and right end.  Now simply
  1166. compute (du,dv) by subtracting and dividing by the length (no
  1167. clamping will be necessary), and use your fast bilinear inner
  1168. loop.  When combined with 3D polygon subdivision, this approach
  1169. can actually be useful.
  1170.  
  1171.   A cheat:
  1172.  
  1173.     When the player is moving, set your internal quality
  1174. settings a little lower.  When the player stops, switch
  1175. back to the normal quality; if the player pauses the game,
  1176. render one frame in normal quality.
  1177.  
  1178.     If done right, you can get a small boost to your fps
  1179. without anyone being able to tell that you did it.  You
  1180. may have to use normal quality if the player is only
  1181. moving very slowly, as well.
  1182.  
  1183.     Note that while this may sound like an utterly cheap
  1184. trick just to improve the on-paper fps number, it's actually
  1185. quite related to the "progressive refinement" approach used
  1186. by some real VR systems (which, when the viewer isn't moving,
  1187. reuse information from the previous frame to allow them to
  1188. draw successive frames with more detail).
  1189.  
  1190.     There are a number of ways of improving this cheat
  1191. intelligently.  If the player is moving parallel to a polygon,
  1192. that polygon will tend to be "stably" texture mapped (similar
  1193. mapping from frame to frame).  If there is any distortion from
  1194. your approximation, this will be visible to the player.  So
  1195. this means a rule of thumb is to only cheat (draw with
  1196. above-average distortion) on polygons that are not facing
  1197. parallel to the direction of motion of the player.
  1198.  
  1199. TEX
  1200. TE   Light My Fire
  1201. T
  1202.  
  1203.     If you're texture mapping, it's generally a good idea
  1204. to light your polygons.  If you don't light them, then it
  1205. may be difficult to see the edge between two walls which
  1206. have the same texture (for instance, check out the "warehouse"
  1207. section of registered DOOM, which is sometimes confusing
  1208. when a near crate looks the same color as a far crate).
  1209.  
  1210.     Lighting is actually pretty straightforward, although
  1211. you take a speed hit in your inner loop.  I'm not going
  1212. to worry about actual lighting models and such; see other
  1213. articles for discussion on how to do light-sourced polygons.
  1214.  
  1215.     Instead I'm going to assume you've computed the lighting
  1216. already.  We'll start with "flat-run" shading, wherein an
  1217. entire run has the same intensity of light falling on it.
  1218.  
  1219.     DOOM uses flat-run shading.  A given polygon has a certain
  1220. amount of light hitting it, which is the same for the entire
  1221. polygon.  In addition, each run of the polygon is sort-of
  1222. lit by the player.  Since runs are always at a constant
  1223. depth, you can use constant lighting across the run and
  1224. still change the brightness with distance from the player
  1225. (DOOM uses something that resembles black fog, technically).
  1226.  
  1227.     So the only real issue is _how_ you actually get the
  1228. lighting to affect the texture.  Several approaches are
  1229. possible, but the only one that I think anyone actually uses
  1230. is with a lighting table.
  1231.  
  1232.     The lighting table is a 2D array.  You use the light
  1233. intensity as one index, and the pixelmap color as the
  1234. other index.  You lookup in the table, and this gives
  1235. you your final output color to display.  (With two
  1236. tables, you can do simple dithering.)  So the only thing
  1237. you have to do is precompute this table.
  1238.  
  1239.     Basically, your inner loop would look something like this:
  1240.  
  1241.   ...compute light...
  1242.   for (i=start_x; i <= end_x; ++i) {
  1243.     color = texture[v >> 16][u >> 16];
  1244.     output = light_table[light][color];
  1245.     screen[i] = output;
  1246.     u += du;
  1247.     v += dv;
  1248.   }
  1249.  
  1250.     The next thing to consider is to Gouraud shade your
  1251. texture map.  To do this, you need to compute the light
  1252. intensity at the left and right edge of the run; look
  1253. elsewhere for more details on Gouraud shading.
  1254.  
  1255.     Once you've got that, you just do something like this:
  1256.  
  1257.   z = light1 << 16;
  1258.   dz = ((light2 - light1) << 16) / (end_x - start_x);
  1259.   for (i=start_x; i <= end_x; ++i) {
  1260.     color = texture[v >> 16][u >> 16];
  1261.     output = light_table[z >> 16][color];
  1262.     screen[i] = color;
  1263.     u += du;
  1264.     v += dv;
  1265.     z += dz;
  1266.   }
  1267.  
  1268.     Note that you shouldn't really do this as I've written the
  1269. code.  light1 and light2 should be calculated with 16 bits of extra
  1270. precision in the first place, rather than having to be shifted
  1271. left when computing z.  I just did it that way so the code
  1272. would be self-contained.
  1273.  
  1274.     I'm going to attempt to give a reasonably fast assembly
  1275. version of this.  However, there's a big problem with doing
  1276. it fast.  The 80x86 only has one register that you can
  1277. address the individual bytes in, and also use for indexing--BX.
  1278. This means that it's a real pain to make our inner loop alternate
  1279. texture map lookup and lighting fetch--whereas it's almost
  1280. trivial on a 680x0.  I avoid this somewhat by processing two
  1281. pixels at a time; first doing two texture map lookups, then
  1282. doing two lighting lookups.
  1283.  
  1284.      Here's a flat-shading inner loop.  I'm doing this code off the
  1285. top of my head, so it may have bugs, but it's trying to show
  1286. at least one way you might try to do this.  Since I use BP,
  1287. I put variables in the FS segment, which means DS points
  1288. to the texture, GS to the lighting table.
  1289.  
  1290.         mov  ch,fs:light
  1291.         adc  ax,ax
  1292. loop8   shr  ax,1          ; restore carry
  1293.         mov  cl,[bx]       ; get first sample, setting up cx for color lookup
  1294.         adc  edx,esi       ; update v-high and u-low
  1295.         adc  ebx,ebp       ; update u-high and v-low
  1296.         mov  bh,dl         ; move v-high into tmap lookup register
  1297.         mov  ah,[bx]       ; get second sample, save it in ah
  1298.         adc  edx,esi
  1299.         adc  ebx,ebp
  1300.         mov  dh,bl         ; save value of bl
  1301.         mov  bx,cx         ; use bx to address color map
  1302.         mov  al,gs:[bx]    ; lookup color for pixel 1
  1303.         mov  bl,ah         ; switch to pixel 2
  1304.         mov  ah,gs:[bx]    ; lookup color for pixel 2
  1305.         mov  es:[di],ax    ; output both pixels
  1306.         mov  bl,dh         ; restore bl from dh
  1307.         mov  bh,dl
  1308.         adc  ax,ax         ; save carry so we can do CMP
  1309.         add  di,2
  1310.         cmp  di,fs:last_di ; rather than having to decrement cx
  1311.         jne  loop8
  1312.  
  1313.     For a Gouraud shading inner loop, we can now have three
  1314. different numbers u, v, and z, which we're all adding at every
  1315. step.  To do this, we use THREE adc, and we have to shuffle
  1316. around which high-bits correspond to which low-bits in a
  1317. complex way.  I'll leave you to figure this out for yourself,
  1318. but here's an attempt at the inner loop.
  1319.  
  1320. loop9   shr  ax,1          ; restore carry
  1321.         mov  al,fs:[bx]    ; get first sample
  1322.         mov  ah,cl         ; save away current z-high into AH
  1323.                            ; this makes AX a value we want to lookup
  1324.         adc  edx,esi       ; update v-high and u-low
  1325.         adc  ebx,ebp       ; update u-high and z-low
  1326.         adc  ecx,z_v_inc   ; update z-high and v-low
  1327.         mov  bh,dl         ; move v-high into tmap lookup register
  1328.         mov  ch,fs:[bx]    ; get second sample, save it in ch
  1329.         mov  dh,bl         ; save value of bl
  1330.         mov  bx,ax
  1331.         mov  al,gs:[bx]    ; lookup first color value
  1332.         mov  bl,ch
  1333.         mov  bh,cl
  1334.         mov  ah,gs:[bx]    ; lookup second color value
  1335.         mov  es:[di],ax    ; output both pixels
  1336.         mov  bl,dh         ; restore bl from dh
  1337.         adc  edx,esi
  1338.         adc  ebx,ebp
  1339.         adc  ecx,z_v_inc
  1340.         mov  bh,dl
  1341.         adc  ax,ax         ; save carry
  1342.         add  di,2
  1343.         cmp  di,last_di    ; rather than having to decrement cx
  1344.         jne  loop9
  1345.  
  1346.     Notice that both of these loops are significantly slower
  1347. than the original loop.  I'm not personally aware of any
  1348. generally faster way to do this sort of thing (but the code
  1349. can be tweaked to be faster).  The one exception is
  1350. that for flat-run shading, you could precompute the entire
  1351. texture with the right lighting.  This would require a lot
  1352. of storage, of course, but if you view it as a cache, it
  1353. would let you get some reuse of information from frame to
  1354. frame, since polygons tend to be lit the same from frame to
  1355. frame.
  1356.  
  1357.     Finally, here's a brief discussion of transparency.
  1358. There are two ways to get transparency effects.  The first
  1359. one is slower, but more flexible.  You use _another_ lookup
  1360. table.  You have to paint the texture that is transparent
  1361. after you've drawn things behind it.  Then, in the inner
  1362. loop, you fetch the texture value (and light it) to draw.
  1363. Then you fetch the pixel that's currently in that location.
  1364. Lookup in a "transparency" table with those two values as
  1365. indices, and write out the result.  The idea is that you
  1366. do this: table[new][old].  If new is a normal, opaque,
  1367. color, then table[new][old] == new, for every value of old.
  1368. If new is a special "color" which is supposed to be transparent,
  1369. then table[new][old] == old, for every value of old.  This causes
  1370. old to show through.  In addition, you can have translucency
  1371. effects, where table[new][old] gives a mixture of the colors
  1372. of old and new.  This will let you do effects like the
  1373. translucent ghosts in the Ultima Underworlds.
  1374.  
  1375.     However, the above approach is extremely slow, since you
  1376. have to load the value from the pixel map and do the extra
  1377. table lookup.  But it works for arbitrary polygons.   DOOM
  1378. only allows transparency on walls, not on ceilings and floors.
  1379. Remember we noticed that the special thing about walls is
  1380. that u is constant as you draw a column from a wall; you
  1381. are walking down a column in the texture map at the same
  1382. time you are drawing a column on screen.  What this means
  1383. is that you can use a data structure which encodes where
  1384. the transparency in each column of the texture map is, and
  1385. use that _outside_ the inner loop to handle transparency.
  1386. For example, your data structure tells you that you have
  1387. a run of 8 opaque pixels, then 3 transparent pixels, then
  1388. 5 more opaque ones.  You scale 8, 3, and 5 by the rate at
  1389. which you're walking over the textures, and simply treat
  1390. this as two seperate opaque runs.
  1391.  
  1392.     The details of this method depend on exactly how you're
  1393. doing your hidden surface removal, and since it doesn't
  1394. generalize to floors&ceilings, much less to arbitrarily
  1395. angled polygons, I don't think going into further detail
  1396. will be very useful (I've never bothered writing such a
  1397. thing, but I'm pretty sure that's all there is to it).
  1398.  
  1399.  
  1400. TEX
  1401. TE   The Postman Always Rings Twice
  1402. T
  1403.  
  1404.     If you're going to write to a slow 16-bit VGA card, you
  1405. should try your darndest to always write 2 pixels at a time.
  1406.  
  1407.     For texture mapping, your best bet is to build your screen
  1408. in a buffer in RAM, and then copy it to the VGA all at once.
  1409. You can do this in Mode 13h or in Mode X or Y, as your heart
  1410. desires.  You should definitely do this if you're painting
  1411. pixels more than once while drawing.
  1412.  
  1413.     If, however, you wish to get a speedup by not paying
  1414. for the extra copy, you might like to write directly to the
  1415. VGA card from your inner loop.
  1416.  
  1417.     You might not think this is very interesting.  If the
  1418. write to the screen buffer in regular RAM is fast, how much
  1419. can you gain by doing both steps at once, instead of splitting
  1420. them in two?
  1421.  
  1422.     The reason it is interesting is because the VGA, while
  1423. slow to accept multiple writes, will let you continue doing
  1424. processing after a single write.  What this means is that
  1425. if you overlap your texture mapping computation with your
  1426. write to the VGA, you can as much as double your speed on
  1427. a slow VGA card.  For example, the fastest I can blast my
  1428. slow VGA card is 45 fps.  I can texture map floor-style directly
  1429. to it at 30 fps.  If I texture map to a memory buffer,
  1430. this is still somewhat slow, more than just the difference
  1431. between the 30 and 45 fps figures.  Thus, my total rate if
  1432. I write to an offscreen buffer drops as low as 20 fps, depending
  1433. on exactly what I do in the texture map inner loop.
  1434.  
  1435.     Ok, so, now suppose you've decided it might be a speedup
  1436. to write directly to the VGA.  There are two problems.  First
  1437. of all, if you're in mode X or Y, it's very difficult to
  1438. write two bytes at a time, which is necessary for this
  1439. approach to be a win.  Second of all, even in mode 13h, it's
  1440. difficult to write two bytes at a time when you're drawing
  1441. a column of pixels.
  1442.  
  1443.     I have no answer here.  I expect people to stick to
  1444. offscreen buffers, or to simply process columns at a time
  1445. and write (at excruciatingly slow rates on some cards) to
  1446. the VGA only one byte at a time.
  1447.  
  1448.     One option is to set up a page flipping mode 13h (which
  1449. is possible on some VGA cards), and to paint two independent
  1450. but adjacent columns at the same time, so that you can write
  1451. a word at a time.  I have a very simple demo that does the
  1452. latter, but it's not for the faint of heart, and I don't
  1453. think it's a win once you have a lot of small polygons.
  1454.  
  1455.     Another answer is to have a DOOM-style "low-detail"
  1456. mode which computes one pixel, duplicates it, and always
  1457. writes both pixels at the same time.
  1458.  
  1459.     A final answer is just to ignore the market of people
  1460. with slow VGA cards.  I wouldn't be surprised if this
  1461. approach was commonplace in a year or two.  But if you do
  1462. so with commercial software, please put a notice of this
  1463. requirement on the box.
  1464.  
  1465. TEX
  1466. TE   Mipmapping (or is it Mip-Mapping?)
  1467. T
  1468.  
  1469.     Mipmapping is a very straightforward technique that
  1470. can be used to significantly improve the quality of your
  1471. textures, so much so that textures that you could not
  1472. otherwise use because they look ugly become usable.
  1473.  
  1474.     The problem that mipmapping addresses is as follows.
  1475. When a texture is far in the distance, such that its
  1476. on-screen size in pixels is significantly smaller than
  1477. its actual size as a texture, only a small number of
  1478. pixels will actually be visible.  If the texture contains
  1479. areas with lots of rapidly varying high contrast data,
  1480. the texture may look ugly, and, most importantly,
  1481. moire artifacts will occur.  (To see this in DOOM, try
  1482. shrinking the screen to the smallest setting and going
  1483. outside in shareware DOOM.  Many of the buildings will
  1484. show moire patterns.  In registered DOOM, there is
  1485. a black-and-blue ceiling pattern which has very bad
  1486. artifacts if it is brightly lit.  Go to the mission
  1487. with the gigantic round acid pool near the beginning.
  1488. Cheat to get light amplification goggles (or maybe
  1489. invulnerability), and you'll see it.)
  1490.  
  1491.     Mipmapping reduces these artifacts by precomputing
  1492. some "anti-aliased" textures and using them when the
  1493. textures are in the distance.
  1494.  
  1495.     The basic idea is to substitute a texture map half as
  1496. big when the polygon is so small that only every other
  1497. pixel is being drawn anyway.  This texture map contains
  1498. one pixel for every 2x2 square in the original, and is
  1499. the color average of those pixels.
  1500.  
  1501.     For a 64x64 texture map, you'd have the original
  1502. map, a 32x32 map, a 16x16 map, an 8x8 map, etc.
  1503.  
  1504.     The mipmaps will smear out colors and lose details.
  1505. You can best test them by forcing them to be displayed
  1506. while they're still close to you; once they appear to
  1507. be working, set them up as described above.
  1508.  
  1509.     Mipmapping causes a somewhat ugly effect when you
  1510. see the textures switch from one mipmap to the next.
  1511. However, especially for some textures, it is far less
  1512. ugly than the effect you would get without them.
  1513.  
  1514.     For example, a fine white-and-black checkerboard
  1515. pattern (perhaps with some overlaid text) would look
  1516. very ugly without mipmapping, as you would see random
  1517. collections of white and black pixels (which isn't too
  1518. bad), and you would see curving moire patterns (which
  1519. is).  With mipmapping, at a certain distance the whole
  1520. polygon would turn grey.
  1521.  
  1522.     I do not believe any existing games for the PC
  1523. use mipmapping.  However, examining the data file
  1524. for the Amiga demo version of Legends of Valour showed
  1525. smaller copies of textures, which made it look like
  1526. mipmapping was being used.
  1527.  
  1528.     Mipmapping requires 33% extra storage for the
  1529. extra texture maps (25% for the first, 25% of 25%
  1530. for the second, etc.).
  1531.  
  1532.     This may also be a good idea for 2D bitmaps which
  1533. are scaled (e.g. critters in Underworld & DOOM, or
  1534. ships in Wing Commander II--although none of those
  1535. appeared to use it.)
  1536.  
  1537.     SGI's Reality Engine does mipmapping.  Actually,
  1538. it does a texturemap lookup on two of the mipmaps,
  1539. the "closer" one and the "farther" one, and uses
  1540. a weighted average between them depending on which
  1541. size is closer to correct.  (The RE also does
  1542. anti-aliasing, which helps even more.)
  1543.  
  1544.  
  1545. TEXTURE
  1546. TEXUE
  1547. TXR    Where Do We Go From Here?
  1548. X
  1549.  
  1550.     The above discussion mostly covers what is basically
  1551. the state of the art of texture mapping on the PC.  Hopefully
  1552. in the future every game will be at least as fast as the
  1553. inner loops in this article allow.
  1554.  
  1555.     As long as people want full-screen images, it'll
  1556. be a while before we have enough computational power
  1557. to do more than that.  But if we did have more power,
  1558. what else could we do with it?
  1559.  
  1560.     o  Better lighting
  1561.       o  Colored lighting (requires complex lookup tables)
  1562.       o  Phong shading (interpolation of normals--one sqrt() per pixel!)
  1563.     o  Higher resolution (640x400, or 640x400 and anti-alias to 320x200)
  1564.     o  A lot more polygons
  1565.     o  Bump mapping (can be done today with huge amounts of precomputation)
  1566.     o  Curved surfaces
  1567.